//Used for sprintf debugging
#include <stdio.h>

#include <ctime>
#include <cstdlib>
#include "main.hpp"
#include <math.h>

using namespace std;

//skewing/unskewing factors
const static float F2 = 0.5*(sqrt(3.0)-1.0);
const static float G2 = (3.0-sqrt(3.0))/6.0;

#define GRADIENTS 32
#define GRADIENTS_MASK 31
#define PI 3.14159265
static float grad2[GRADIENTS][2];

//permutations array
#define PERMUTATIONS 512
#define PERMUTATIONS_MASK 0xff
static int perm[512] = {};
static int initialized = 0;
static unsigned int last_seed = 0;

void init(unsigned int seed){
    if(seed == 0){
        seed = time(0);
    }

    if(seed == 0 || last_seed != seed){
        srand(seed);
        for(int i=0; i<512; ++i){
            perm[i] = rand() & PERMUTATIONS_MASK;
        }
        last_seed = seed;
    }

    if(initialized == 0){
        float angle = PI / (2 * GRADIENTS);
        float theta = 0;
        float x, y;

        for(int i=3; i<GRADIENTS; i+=4){
            x = cos(theta);
            y = sin(theta);
            grad2[i][0] = x;
            grad2[i][1] = y;
            grad2[i-1][0] = -x;
            grad2[i-1][1] = y;
            grad2[i-2][0] = -x;
            grad2[i-2][1] = -y;
            grad2[i-3][0] = x;
            grad2[i-3][1] = -y;
            theta += angle;
        }

        initialized = 1;
    }
}

float dot(float *g, float x, float y) {
    return g[0]*x + g[1]*y;
}

float noise2d(float x, float y){
    //Skew the input space to determine which simplex cell we're in
    float s = (x+y)*F2;
    int i = floorf(x+s);
    int j = floorf(y+s);

    float t = (i+j)*G2;
    float X0 = i-t;
    float Y0 = j-t;
    float x0 = x-X0;
    float y0 = y-Y0;

    int i1, j1;
    if(x0>y0) {i1=1; j1=0;} // lower triangle, XY order: (0,0)->(1,0)->(1,1)
    else {i1=0; j1=1;}      // upper triangle, YX order: (0,0)->(0,1)->(1,1)

    float x1 = x0 - i1 + G2;
    float y1 = y0 - j1 + G2;
    float x2 = x0 - 1.0 + 2.0 * G2;
    float y2 = y0 - 1.0 + 2.0 * G2;

    int ii = i & PERMUTATIONS_MASK;
    int jj = j & PERMUTATIONS_MASK;
    int gi0 = perm[ii+perm[jj]] & GRADIENTS_MASK;
    int gi1 = perm[ii+i1+perm[jj+j1]] & GRADIENTS_MASK;
    int gi2 = perm[ii+1+perm[jj+1]] & GRADIENTS_MASK;

   // Calculate the contribution from the three corners
    float n0, n1, n2;   //Noise contributions from the three corners
    float t0 = 0.5 - x0*x0-y0*y0;
    if(t0<0) n0 = 0.0;
    else {
      t0 *= t0;
      n0 = t0 * t0 * dot(grad2[gi0], x0, y0);  // (x,y) of grad3 used for 2D gradient
    }
    float t1 = 0.5 - x1*x1-y1*y1;
    if(t1<0) n1 = 0.0;
    else {
      t1 *= t1;
      n1 = t1 * t1 * dot(grad2[gi1], x1, y1);
    }
    float t2 = 0.5 - x2*x2-y2*y2;
    if(t2<0) n2 = 0.0;
    else {
      t2 *= t2;
      n2 = t2 * t2 * dot(grad2[gi2], x2, y2);
    }
    // Add contributions from each corner to get the final noise value.
    // The result is scaled to return values in the interval [-1,1].
    return 70.0 * (n0 + n1 + n2);
}

static char *result = NULL;

float modify_none(float dx, float dy, float z){
    return z;
}

float modify_circle(float dx, float dy, float z){
    z *= 1 - sqrt(dx*dx + dy*dy);
    if(z < 0){
        return 0;
    }
    return z;
}

float modify_square(float dx, float dy, float z){
    if(dx < dy){
        dx = dy;
    }
    z *= 1.0 - dx;
    return z;
}

#define ASCII_RANGE_MIN 2
int ascii_map(float x0, float y0, int mapwidth, int mapheight, float mapscale, char ascii_min, char ascii_max, float (*modify)(float,float,float), int flatten){
    float ascii_range = ascii_max - ascii_min;
    if(++ascii_range < ASCII_RANGE_MIN){
        return 1;
    }

    int mapsize = mapwidth*mapheight;

    //allocate space for float heightmap
    float *zmap;
    try{zmap = new float[mapsize];}catch(...){return 2;}

    float minz = 1; //this will store the lowest z value - used for scaling later on
    float maxz = -1; //this will store the highest z value - used for scaling later on
    float z=0;
    int i=0;
    for(int x=0; x<mapwidth; ++x){
        for(int y=0; y<mapheight; ++y){
            z = noise2d(x0+x*mapscale, y0+y*mapscale) * 0.5 + 0.5;    //[0,1]

            float dx = ((float)(x+x)/(float)mapwidth)-1.0;//(float)(x+x-mapwidth)/(float)(mapwidth); //fabs((2*(float)x/(float)mapwidth)-1);
            float dy = ((float)(y+y)/(float)mapheight)-1.0;//(float)(y+y-mapheight)/(float)(mapheight);//fabs((2*(float)y/(float)mapheight)-1);

            z = modify(fabs(dx), fabs(dy), z);

            z = pow(z,flatten);


            if(z < minz){
                minz = z;
            }
            if(z > maxz){
                maxz = z;
            }
            zmap[i++] = z;
        }
    }

    try{result = new char[mapsize + 1];}catch(...){return 3;}

    float zrange = maxz - minz;
    if(zrange <= 0.0){
        zrange = 1;
    }
    float multiplier = ascii_range / zrange;
    char c;
    for(i=0; i<mapsize; ++i){
        c = ((zmap[i]-minz)*multiplier) + ascii_min;

        if(c > ascii_max){
            c = ascii_max;
        }else if(c < ascii_min){
            c = ascii_min;
        }
        result[i] = c;
    }
    result[mapsize] = '\0';

    delete[] zmap;

    return 0;
}
#undef ASCII_RANGE_MIN
/*
    argv[0]: x
    argv[1]: y
    argv[2]: mapwidth
    argv[3]: mapheight
    argv[4]: mapscale
    argv[5]: asciimin
    argv[6]: asciimax
    argv[7]: seed
    argv[8]: modify
*/
char *generate_map(int argc, char *argv[]){
    //free result array
    if(result){
        delete[] result;
        result = NULL;
    }

    float x=1, y=1, mapscale=0.05, flatten=1;
    char asciimin='0', asciimax='9';
    unsigned int seed=0;
    int mapwidth=50, mapheight=50;
    float (*modify)(float,float,float) = &modify_none;

    //read & convert arguments
    switch(argc){
    case 10: //flatten
        flatten = atof(argv[9]);
        if(flatten == 0.0){
            flatten = 1;
        }
    case 9: //modify
        switch(argv[8][0]){
        case 's':
            modify = &modify_square;
            break;
        case 'c':
            modify = &modify_circle;
            break;
        default:
            break;
        }
    case 8: //seed
        seed = atoi(argv[7]);
    case 7: //asciimin asciimax
        if(argv[6][0] != '\0'){
            asciimax = argv[6][0];
        }
        if(argv[5][0] != '\0'){
            asciimin = argv[5][0];
        }
        if(asciimin > asciimax){
            char temp = asciimin;
            asciimin = asciimax;
            asciimax = temp;
        }
    case 5: //mapscale
        mapscale = atof(argv[4]);
        if(mapscale == 0.0){
            mapscale = 0.05;
        }
    case 4: //mapheight mapwidth
        mapheight = atoi(argv[3]);
        mapwidth = atoi(argv[2]);
        if(mapwidth <= 0 || mapwidth <= 0){
            return 0;
        }
        y = atof(argv[1]);
        x = atof(argv[0]);
        break;
    default:
        return 0;
    }

    init(seed);

    int exitcode = ascii_map(x, y, mapwidth, mapheight, mapscale, asciimin, asciimax, modify, flatten);
    if(exitcode == 0){
        return result;
    }else{
        try{result = new char[2];}catch(...){return 0;}
        result[0] = exitcode;
        result[1] = '\0';
    }
    return 0;
}
